package nachos.threads;

import nachos.ag.BoatGrader;

public class Boat {

    static enum Local {

        Oahu, Molokai
    }
    static BoatGrader bg;
    static boolean status;
    static int childrenInOahu, childrenInMolokai, adultsInOahu, childrenInBoat,
            wakedChildrenInOahu, wakedAdultsInOahu;
    
    static boolean adultInBoat;
    static Local boatLocal;
    static Lock boatLock;
    static Condition cChildrenInOahu;
    static Condition cChildRide;
    static Condition cChildMolokai;
    static Condition cAdultInOahu;

    public static void selfTest() {
        BoatGrader b = new BoatGrader();

        System.out.println("\n ***Testing Boats with only 2 children***");
        begin(0, 2, b);

    	System.out.println("\n ***Testing Boats with 2 children, 1 adult***");
      	begin(1, 2, b);

      	System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
      	begin(3, 3, b);
    }

    public static void begin(int adults, int children, BoatGrader b) {
        // Store the externally generated autograder in a class
        // variable to be accessible by children.
        bg = b;

        // barco esta funcionando
        status = true;
        if (children < 2) {
            System.out.println("Número insuficiente de criancas.");
            return;
        }

        boatLock = new Lock();
        cChildrenInOahu = new Condition(boatLock);
        cChildRide = new Condition(boatLock);
        cChildMolokai = new Condition(boatLock);
        cAdultInOahu = new Condition(boatLock);

        // Instantiate global variables here

        adultsInOahu = adults;
        childrenInOahu = children;
        childrenInMolokai = 0;
        childrenInBoat = 0;

        wakedChildrenInOahu = children;
        wakedAdultsInOahu = adults;

        boatLocal = Local.Oahu;

        // Create threads here. See section 3.4 of the Nachos for Java
        // Walkthrough linked from the projects page.

        Runnable r = new Runnable() {
            public void run() {
                SampleItinerary();
            }
        };
        
        KThread criancasEAdultos[] = new KThread[adults + children];
        int k = 0;

        for (int i = 0; i < adults; i++) {
            criancasEAdultos[k] = new KThread(r);
            criancasEAdultos[k].setName("Adult");
            criancasEAdultos[k].fork();
            
            k++;
        }

        for (int i = 0; i < children; i++) {
            criancasEAdultos[k] = new KThread(r);
            criancasEAdultos[k].setName("Child");
            criancasEAdultos[k].fork();
            
            k++;
        }
        
        for (int i = 0; i < k; i++) {
            criancasEAdultos[i].join();
        }
    }

    static void AdultItinerary() {
        Local localAdultoAtual = Local.Oahu;
        

        // um adulto nao acorda durante uma 
        // viagem, pois pode ser a viagem 
        // de uma crianca que vai acorda-lo
        boatLock.acquire();
        
        wakedAdultsInOahu--;
        cAdultInOahu.sleep();
        wakedAdultsInOahu++;
        
        boatLock.release();

        // uma crianca acordou esse adulto
        while (true) {
            if (localAdultoAtual == Local.Oahu) {
                boatLock.acquire();

                if (wakedAdultsInOahu > 1) {
                    wakedAdultsInOahu--;
                    cAdultInOahu.sleep();
                    wakedAdultsInOahu++;
                }
                
                if (boatLocal == Local.Oahu) {
                    bg.AdultRowToMolokai();
                    
                    wakedAdultsInOahu--;
                    adultsInOahu--;
                    
                    boatLocal = Local.Molokai;
                    cChildMolokai.wake();
                    
                    boatLock.release();
                    return;
                }

                boatLock.release();
            }
        }

    }

    static void ChildItinerary() {
        Local localCriancaAtual = Local.Oahu;

        while (true) {
            while (localCriancaAtual == Local.Oahu) {
                boatLock.acquire();

                while (wakedChildrenInOahu > 2 || boatLocal != Local.Oahu
                        || childrenInBoat == 2) {
                    wakedChildrenInOahu--;
                    cChildrenInOahu.sleep();
                    wakedChildrenInOahu++;
                }

                // fim do programa, todos passaram para molokai
                if (childrenInBoat == 1 && childrenInOahu == 1
                        && adultsInOahu == 0) {

                    boatLocal = Local.Molokai;
                    localCriancaAtual = Local.Molokai;

                    childrenInOahu--;
                    wakedChildrenInOahu--;
                    childrenInMolokai++;

                    cChildRide.wake();
                    bg.ChildRideToMolokai();

                    childrenInBoat = 0;

                    status = false;
                    boatLock.release();

                    return;
                }

                // a thread atual e uma crianca e nao 
                // ha pessoas no barco, entra no barco 
                // e espera outra crianca
                if (childrenInBoat == 0) {
                    bg.ChildRowToMolokai();

                    childrenInBoat++;
                    childrenInOahu--;
                    wakedChildrenInOahu--;

                    cChildRide.sleep();

                    childrenInMolokai++;
                    childrenInBoat--;

                    localCriancaAtual = Local.Molokai;
                } else if (childrenInBoat == 1) {
                    // uma crianca esta no barco a thread
                    // atual (crianca) pega carona

                    childrenInBoat++;

                    localCriancaAtual = Local.Molokai;
                    boatLocal = Local.Molokai;

                    childrenInOahu--;
                    wakedChildrenInOahu--;
                    childrenInMolokai++;

                    cChildRide.wake();
                    childrenInBoat--;

                    bg.ChildRideToMolokai();
                }

                boatLock.release();
            }

            while (localCriancaAtual == Local.Molokai) {
                boatLock.acquire();

                // crianca que deu carona para a ultima em Oahu
                // todos ja passaram para molokai, agora.
                if (status == false) {
                    boatLock.release();
                    return;
                }

                // mais que duas criancas estao em 
                // malokai uma delas pode ir embora
                if (childrenInMolokai > 2) {
                    childrenInMolokai--;

                    boatLock.release();
                    return;
                }

                // uma crianca chega em molokai
                if (boatLocal == Local.Molokai) {

                    // se o barco estiver ocupado, dorme
                    if (childrenInBoat == 1) {
                        cChildMolokai.sleep();
                    }

                    // esta e a unica crianca em molokai
                    if (childrenInMolokai == 1) {

                        bg.ChildRowToOahu();

                        childrenInMolokai--;
                        childrenInOahu++;

                        boatLocal = Local.Oahu;
                        localCriancaAtual = Local.Oahu;

                        cChildrenInOahu.wake();

                    } else if (childrenInMolokai == 2) {
                        // existem duas criancas em
                        // molokai, chama uma adulto

                        bg.ChildRowToOahu();

                        childrenInMolokai--;
                        childrenInOahu++;
                        wakedChildrenInOahu++;

                        boatLocal = Local.Oahu;
                        localCriancaAtual = Local.Oahu;

                        // existe um adulto em oahu,
                        // ele acorda para fazer a viagem
                        if (adultsInOahu > 0) {
                            cAdultInOahu.wake();

                            wakedChildrenInOahu--;
                            cChildrenInOahu.sleep();
                            wakedChildrenInOahu++;
                        } else {
                            // acorda outra crianca em oahu
                            // para que duas facam a viagem
                            cChildrenInOahu.wake();
                        }
                    }
                }

                boatLock.release();
            }
        }
    }

    static void SampleItinerary() {
        if (KThread.currentThread().getName().equals("Adult")) {
            AdultItinerary();
        } else if (KThread.currentThread().getName().equals("Child")) {
            ChildItinerary();
        }

    }
}
